from enum import Enum
from typing import List, Optional

from pydantic import BaseModel

from mock_db import REPORT_CONFIGS


class Position(str, Enum):
    SELECT = "select"
    WHERE = "where"
    GROUP_BY = "group by"
    HAVING = "having"
    ORDER_BY = "order by"


def where_sql(condition: List[str]) -> str:
    operation = condition[0]
    if operation in "between":
        return f"({condition[1]} {condition[0]} {condition[2]} and {condition[3]})"
    elif operation in "in":
        return f"({condition[1]} {condition[0]} {condition[2:]})"
    return f"({condition[1]} {condition[0]} {condition[2]})"


class Condition(BaseModel):
    id: int
    report_id: int
    position: Position
    values: list

    def to_sql(self) -> str:
        if self.position == Position.WHERE or self.position==Position.HAVING:
            return " and ".join([where_sql(i) for i in self.values])
        elif self.position == Position.ORDER_BY:
            return str.join(",", [" ".join(i) for i in self.values])
        return ",".join(self.values)


class AggregationFunc(Enum):
    COUNT = "count"
    COUNT_IF = "countIf"
    COUNT_DISTINCT = "uniqExtra"
    COUNT_DISTINCT_IF = "uniqExtraIf"
    SUM = "sum"
    SUM_IF = "sumIf"
    AVG = "avg"


class Metric(BaseModel):
    id: int
    name: str
    sql: str
    depend_tables: Optional[List[str]]
    label: Optional[str]


class Aggregation(BaseModel):
    id: int
    report_id: int
    metric: Metric
    conditions: List[Condition]

    def to_sql(self):
        return f"{self.func}()"


class Report(BaseModel):
    id: int
    name: str
    base_sql: str
    aggregations: List[Aggregation]
    conditions: List[Condition]
    result: Optional[dict]


class SqlBuilder:
    report: Report
    select_part: str
    where_part: str
    group_part: str
    order_part: str
    from_part: str

    def __init__(self, report: Report):
        self.report = report

    def render_sql(self):
        res = {
            "from": "",
            "aggregation": self.report.aggregations,
            "select": "",
            "where": "",
            "group by": "",
            "having": "",
            "order by": "",
        }
        sql_strings = []
        for aggregation in report.aggregations:
            res["from"] = aggregation.metric.sql
            for i in report.conditions + aggregation.conditions:
                res[i.position.value] = f"\n{i.position} {i.to_sql()}"

            sql_strings.append("{select} ,{from} {where} {group by} {having} {order by}".format(**res))
        return "\n) join on a=b (\n".join(sql_strings)


if __name__ == '__main__':
    conditions = Condition(**REPORT_CONFIGS[0]["conditions"][0])
    report = Report(**REPORT_CONFIGS[0])
    sql = SqlBuilder(report).render_sql()
    print(sql)
